package br.senai.sc.controller.business;

import java.math.BigDecimal;

import br.senai.sc.controller.exception.BusinessException;
import br.senai.sc.model.Amostra;
import br.senai.sc.model.PontoColeta;

/**
 * Contém os cálculos de IQA para o parâmetro OD.
 * 
 * @author Tiago
 */
public final class CalculoODBusiness {

	/**
	 * Peso fixo utilizado para o cálculo da qualidade do OD (qi^w)
	 */
	private static final double PESO = 0.17;

	/**
	 * Utilizado para a quantidade de casas decimais devem ser utilizadas para o
	 * retorno do calculo de cada intervalo de valor.
	 */
	public static final int ESCALA = 10;

	/**
	 * Utilizado para a quantidade de casas decimais devem ser utilizadas para o
	 * cálculo de exponenciais.
	 */
	private static final int ESCALA_EXP = 20;

	/**
	 * Define o intervalo para o cálculo dos coliformes fecais. De mais do que 0
	 * a menos que 100.
	 */
	public static final int[] PRIMEIRO_INTERVALO = { 1, 100 };

	/**
	 * Define o intervalo para o cálculo dos coliformes fecais. De 100 a menos
	 * que 140.
	 */
	public static final int[] SEGUNDO_INTERVALO = { 100, 140 };

	private CalculoODBusiness() {

	}

	/**
	 * Cálculo realizado:
	 * (14,62-0,3898*Temeratura+0,006969*Temperatura^2-0,00005896
	 * *Temperatura^3)*(1-0,0000228675*Altitude)^5,167
	 * 
	 * @return Concetração de saturação do ponto.
	 */
	private static BigDecimal concSaturacao(Amostra amostra) {

		PontoColeta pontoColeta = amostra.getPontoColeta();
		if ((amostra.getTemperatura() == null) || (pontoColeta == null)
				|| (pontoColeta.getAltitude() == null)) {
			throw new IllegalArgumentException(
					"Valor necessário para cálculo não informado.");
		}

		BigDecimal temperatura = amostra.getTemperatura();
		BigDecimal altitude = amostra.getPontoColeta().getAltitude();

		BigDecimal temperaturaExp2 = new BigDecimal(Math.pow(
				temperatura.doubleValue(), 2)).setScale(ESCALA_EXP,
				IQABusiness.ROUND_MODE);
		BigDecimal temperaturaExp3 = new BigDecimal(Math.pow(
				temperatura.doubleValue(), 3)).setScale(ESCALA_EXP,
				IQABusiness.ROUND_MODE);

		BigDecimal exp = new BigDecimal(Math.pow(
				BigDecimal.ONE.subtract(
						new BigDecimal("0.0000228675").multiply(altitude))
						.doubleValue(), 5.167));

		BigDecimal retorno =

		new BigDecimal("14.62")
				.subtract(new BigDecimal("0.3898").multiply(temperatura))
				.add(new BigDecimal("0.006969").multiply(temperaturaExp2))
				.subtract(
						new BigDecimal("0.00005896").multiply(temperaturaExp3))
				.multiply(exp);

		return retorno.setScale(ESCALA, IQABusiness.ROUND_MODE);
	}

	/**
	 * Utilizado para fazer o cálculo do OD caso o valor informado é está entre
	 * 0 e 100. Obs: este método não considera o valor 100, o valor deve ser
	 * menor que 100. Cálculo realizado: 0,00000000101307189533432*[valor]^6 -
	 * 0,000000251036953231865*[valor]^5 + 0,0000180332453547649*[valor]^4 -
	 * 0,000198705094646812*[valor]^3 - 0,00852221706372802*[valor]^2 +
	 * 0,758129540663504*[valor] + 1,05563561210511
	 * 
	 * @param valor
	 * @return
	 * @throws BusinessException
	 *             Lançada caso o valor não esteja entre o intervalo correto
	 */
	private static BigDecimal primeiroIntervalo(BigDecimal valor)
			throws BusinessException {

		if ((valor.compareTo(new BigDecimal(PRIMEIRO_INTERVALO[0])) <= 0)
				|| (valor.compareTo(new BigDecimal(PRIMEIRO_INTERVALO[1])) >= 0)) {
			throw new BusinessException();
		}

		BigDecimal valorExp6 = new BigDecimal(Math.pow(valor.doubleValue(), 6))
				.setScale(ESCALA_EXP, IQABusiness.ROUND_MODE);
		BigDecimal valorExp5 = new BigDecimal(Math.pow(valor.doubleValue(), 5))
				.setScale(ESCALA_EXP, IQABusiness.ROUND_MODE);
		BigDecimal valorExp4 = new BigDecimal(Math.pow(valor.doubleValue(), 4))
				.setScale(ESCALA_EXP, IQABusiness.ROUND_MODE);
		BigDecimal valorExp3 = new BigDecimal(Math.pow(valor.doubleValue(), 3))
				.setScale(ESCALA_EXP, IQABusiness.ROUND_MODE);
		BigDecimal valorExp2 = new BigDecimal(Math.pow(valor.doubleValue(), 2))
				.setScale(ESCALA_EXP, IQABusiness.ROUND_MODE);

		BigDecimal retorno = new BigDecimal("0.00000000101307189533432")
				.multiply(valorExp6)
				.subtract(
						new BigDecimal("0.000000251036953231865")
								.multiply(valorExp5))
				.add(new BigDecimal("0.0000180332453547649")
						.multiply(valorExp4))
				.subtract(
						new BigDecimal("0.000198705094646812")
								.multiply(valorExp3))
				.subtract(
						new BigDecimal("0.00852221706372802")
								.multiply(valorExp2))
				.add(new BigDecimal("0.758129540663504").multiply(valor))
				.add(new BigDecimal("1.05563561210511"));

		return retorno.setScale(ESCALA, BigDecimal.ROUND_DOWN);
	}

	/**
	 * Utilizado para fazer o cálculo do OD caso o valor informado é maior ou
	 * igual a 100 e menor ou igual a 140. Cálculo realizado:
	 * -0,0000125000000000264*[valor]^4 + 0,00591666666668278*[valor]^3 -
	 * 1,04875000000354*[valor]^2 + 82,0083333336592*[valor] - 2281,00000001055
	 * 
	 * @param valor
	 * @return
	 * @throws BusinessException
	 *             Lançada caso o valor não esteja entre o intervalo correto
	 */
	private static BigDecimal segundoIntervalo(BigDecimal valor)
			throws BusinessException {

		if ((valor.compareTo(new BigDecimal(SEGUNDO_INTERVALO[0])) < 0)
				|| (valor.compareTo(new BigDecimal(SEGUNDO_INTERVALO[1])) >= 0)) {
			throw new BusinessException();
		}

		BigDecimal valorExp4 = new BigDecimal(Math.pow(valor.doubleValue(), 4))
				.setScale(ESCALA_EXP, IQABusiness.ROUND_MODE);
		BigDecimal valorExp3 = new BigDecimal(Math.pow(valor.doubleValue(), 3))
				.setScale(ESCALA_EXP, IQABusiness.ROUND_MODE);
		BigDecimal valorExp2 = new BigDecimal(Math.pow(valor.doubleValue(), 2))
				.setScale(ESCALA_EXP, IQABusiness.ROUND_MODE);

		// -0,0000125000000000264*[valor]^4 + 0,00591666666668278*[valor]^3 -
		// 1,04875000000354*[valor]^2 + 82,0083333336592*[valor] -
		// 2281,00000001055

		BigDecimal retorno = new BigDecimal("0.0000125000000000264")
				.multiply(valorExp4)
				.negate()
				// -0,0000125000000000264*[valor]^4
				.add(new BigDecimal("0.00591666666668278").multiply(valorExp3))
				// 0,00591666666668278*[valor]^3
				.subtract(
						new BigDecimal("1.04875000000354").multiply(valorExp2))
				// - 1,04875000000354*[valor]^2
				.add(new BigDecimal("82.0083333336592").multiply(valor))
				// + 82,0083333336592*[valor]
				.subtract(new BigDecimal("2281.00000001055"));// -
																// 2281,00000001055

		return retorno.setScale(ESCALA, IQABusiness.ROUND_MODE);
	}

	/**
	 * Utilizado para fazer o cálculo do OD caso o valor informado é igual a
	 * 140. Cálculo realizado: -0,0000125000000000264*valor^4 +
	 * 0,00591666666668278*valor^3 - 1,04875000000354*valor^2 +
	 * 82,0083333336592*valor - 2281,00000001055;
	 * 
	 * @param valor
	 * @return
	 * @throws BusinessException
	 *             Lançada caso o valor não esteja entre o intervalo correto
	 */
	private static BigDecimal igualFimUltimoIntervalo(BigDecimal valor)
			throws BusinessException {

		if (valor.compareTo(new BigDecimal(SEGUNDO_INTERVALO[1])) != 0) {
			throw new BusinessException();
		}

		BigDecimal valorExp4 = new BigDecimal(Math.pow(valor.doubleValue(), 4))
				.setScale(ESCALA_EXP, IQABusiness.ROUND_MODE);
		BigDecimal valorExp3 = new BigDecimal(Math.pow(valor.doubleValue(), 3))
				.setScale(ESCALA_EXP, IQABusiness.ROUND_MODE);
		BigDecimal valorExp2 = new BigDecimal(Math.pow(valor.doubleValue(), 2))
				.setScale(ESCALA_EXP, IQABusiness.ROUND_MODE);

		// -0,0000125000000000264*valor^4 + 0,00591666666668278*valor^3 -
		// 1,04875000000354*valor^2 + 82,0083333336592*valor - 2281,00000001055;
		BigDecimal retorno = new BigDecimal("0.0000125000000000264")
				.multiply(valorExp4)
				.negate()
				// -0,0000125000000000264*valor^4
				.add(new BigDecimal("0.00591666666668278").multiply(valorExp3))
				// + 0,00591666666668278*valor^3
				.subtract(
						new BigDecimal("1.04875000000354").multiply(valorExp2))
				// - 1,04875000000354*valor^2
				.add(new BigDecimal("82.0083333336592").multiply(valor))
				// + 82,0083333336592*valor
				.subtract(new BigDecimal("2281.00000001055")); // -
																// 2281,00000001055

		return retorno.setScale(ESCALA, IQABusiness.ROUND_MODE);
	}

	/**
	 * Calcula o IQA do OD informado. Cálculo realizado: Se [valor] < 0 Lança
	 * exceção Se [valor] = 0 IQA = 0 Se [valor] < 100
	 * 
	 * @see {@link CalculoODBusiness#primeiroIntervalo(double)} Se [valor] <=
	 *      140
	 * @see {@link CalculoODBusiness#segundoIntervalo(double)} Se [valor] > 140
	 *      IQA = 50
	 * @param valor
	 * @param amostra
	 *            Amostra utilizada para pegar a altitude e temperatura para o
	 *            calculo da concentração de saturação.
	 * @return
	 * @throws BusinessException
	 *             Lançada caso o valor informado seja inválido (Ex: menor que
	 *             0).
	 */
	public static BigDecimal calculaIQA(Amostra amostra)
			throws BusinessException {

		if (amostra == null) {
			throw new IllegalArgumentException(
					"Informou null para calcular IQA de OD da amostra.");
		}

		// calcula o OD
		// deve-se se informar a precisão das casas para não ter problema com a
		// divisão
		// @see
		// http://jaydeepm.wordpress.com/2009/06/04/bigdecimal-and-non-terminating-decimal-expansion-error/
		BigDecimal valor = (amostra.getOd().divide(concSaturacao(amostra),
				ESCALA_EXP, IQABusiness.ROUND_MODE)).multiply(
				new BigDecimal(100)).setScale(ESCALA, IQABusiness.ROUND_MODE);

		// SE([valor]<0;"ERR";
		// se o valor for menos que 0 o mesmo está incorreto
		if (valor.compareTo(BigDecimal.ZERO) < 0) {
			throw new BusinessException();
		}

		// se o valor for 0 o retorno é 0
		if (valor.compareTo(BigDecimal.ZERO) == 0) {
			return BigDecimal.ZERO;
		}
		if (valor.compareTo(new BigDecimal(PRIMEIRO_INTERVALO[1])) < 0) {
			return primeiroIntervalo(valor);
		}

		if (valor.compareTo(new BigDecimal(SEGUNDO_INTERVALO[1])) < 0) {
			return segundoIntervalo(valor);
		}

		if (valor.compareTo(new BigDecimal(SEGUNDO_INTERVALO[1])) == 0) {
			return igualFimUltimoIntervalo(valor);
		}

		// se o valor for maior que 140 retorna sempre 50
		final int retornoMaiorQueFimUltimoIntervalo = 50;
		return new BigDecimal(retornoMaiorQueFimUltimoIntervalo);
	}

	/**
	 * Calcula a média de qualidade (qi^w) do OD.
	 * 
	 * @param valor
	 * @return
	 * @throws BusinessException
	 *             Lançada caso o valor informado seja inválido.
	 */
	protected static BigDecimal mediaQualidade(Amostra amostra)
			throws BusinessException {
		return new BigDecimal(Math.pow(calculaIQA(amostra).doubleValue(), PESO));
	}

}
